import outdent from "outdent";
import * as Protocol from "vscode-languageserver-protocol";
import * as Types from "vscode-languageserver-types";
import * as LanguageServer from "./../src/LanguageServer";

const describe_opt = LanguageServer.ocamlVersionGEq("4.08.0")
  ? describe
  : xdescribe;

describe_opt("textDocument/completion", () => {
  let languageServer: LanguageServer.LanguageServer;

  function openDocument(source: string) {
    languageServer.sendNotification(
      Protocol.DidOpenTextDocumentNotification.type,
      {
        textDocument: Types.TextDocumentItem.create(
          "file:///test.ml",
          "ocaml",
          0,
          source,
        ),
      },
    );
  }

  async function querySignatureHelp(position: Types.Position) {
    return await languageServer.sendRequest(
      Protocol.SignatureHelpRequest.type,
      {
        textDocument: Types.TextDocumentIdentifier.create("file:///test.ml"),
        position,
      },
    );
  }

  beforeEach(async () => {
    languageServer = await LanguageServer.startAndInitialize({
      capabilities: {
        textDocument: {
          moniker: {},
          signatureHelp: {
            dynamicRegistration: true,
            signatureInformation: {
              documentationFormat: ["markdown", "plaintext"],
              parameterInformation: {
                labelOffsetSupport: true,
              },
            },
          },
        },
      },
    });
  });

  afterEach(async () => {
    await LanguageServer.exit(languageServer);
  });

  it("can provide signature help after a function-type value", async () => {
    openDocument(outdent`
      let map = ListLabels.map

      let _ = map
    `);

    const items = await querySignatureHelp(Types.Position.create(2, 11));
    expect(items).toMatchInlineSnapshot(`
{
  "activeParameter": 1,
  "activeSignature": 0,
  "signatures": [
    {
      "label": "map : f:('a -> 'b) -> 'a list -> 'b list",
      "parameters": [
        {
          "label": [
            6,
            18,
          ],
        },
        {
          "label": [
            22,
            29,
          ],
        },
      ],
    },
  ],
}
`);
  });

  it("can provide signature help for an operator", async () => {
    openDocument(outdent`
      let (+) = (+)

      let _ = 1 + 2
    `);

    const items = await querySignatureHelp(Types.Position.create(2, 13));
    expect(items).toMatchInlineSnapshot(`
{
  "activeParameter": 1,
  "activeSignature": 0,
  "signatures": [
    {
      "label": "(+) : int -> int -> int",
      "parameters": [
        {
          "label": [
            6,
            9,
          ],
        },
        {
          "label": [
            13,
            16,
          ],
        },
      ],
    },
  ],
}
`);
  });

  it("can provide signature help for an anonymous function", async () => {
    openDocument(outdent`
      let _ = (fun x -> x + 1)
    `);

    const items = await querySignatureHelp(Types.Position.create(0, 26));
    expect(items).toMatchInlineSnapshot(`
{
  "activeParameter": 0,
  "activeSignature": 0,
  "signatures": [
    {
      "label": "_ : int -> int",
      "parameters": [
        {
          "label": [
            4,
            7,
          ],
        },
      ],
    },
  ],
}
`);
  });

  it("can make the non-labelled parameter active", async () => {
    openDocument(outdent`
      let map = ListLabels.map

      let _ = map []
    `);

    const items = await querySignatureHelp(Types.Position.create(2, 14));
    expect(items).toMatchInlineSnapshot(`
{
  "activeParameter": 1,
  "activeSignature": 0,
  "signatures": [
    {
      "label": "map : f:('a -> 'b) -> 'a list -> 'b list",
      "parameters": [
        {
          "label": [
            6,
            18,
          ],
        },
        {
          "label": [
            22,
            29,
          ],
        },
      ],
    },
  ],
}
`);
  });

  it("can make the labelled parameter active", async () => {
    openDocument(outdent`
      let map = ListLabels.map

      let _ = map ~f:Int.abs
    `);

    const items = await querySignatureHelp(Types.Position.create(2, 22));
    expect(items).toMatchInlineSnapshot(`
{
  "activeParameter": 0,
  "activeSignature": 0,
  "signatures": [
    {
      "label": "map : f:(int -> int) -> int list -> int list",
      "parameters": [
        {
          "label": [
            6,
            20,
          ],
        },
        {
          "label": [
            24,
            32,
          ],
        },
      ],
    },
  ],
}
`);
  });

  it("can make a labelled parameter active by prefix", async () => {
    openDocument(outdent`
      let mem = ListLabels.mem

      let _ = mem ~se
    `);

    const items = await querySignatureHelp(Types.Position.create(2, 15));
    expect(items).toMatchInlineSnapshot(`
{
  "activeParameter": 1,
  "activeSignature": 0,
  "signatures": [
    {
      "label": "mem : 'a -> set:'a list -> bool",
      "parameters": [
        {
          "label": [
            6,
            8,
          ],
        },
        {
          "label": [
            12,
            23,
          ],
        },
      ],
    },
  ],
}
`);
  });

  it("can make an optional parameter active by prefix", async () => {
    openDocument(outdent`
      let create = Hashtbl.create

      let _ = create ?ra
    `);

    const items = await querySignatureHelp(Types.Position.create(2, 18));
    expect(items).toMatchInlineSnapshot(`
{
  "activeParameter": 0,
  "activeSignature": 0,
  "signatures": [
    {
      "label": "create : ?random:bool -> int -> ('a, 'b) Hashtbl.t",
      "parameters": [
        {
          "label": [
            9,
            21,
          ],
        },
        {
          "label": [
            25,
            28,
          ],
        },
      ],
    },
  ],
}
`);
  });

  it("can return documentation for the function being applied", async () => {
    openDocument(
      outdent`
      (** This is an example of a docstring that demonstrates various ocamldoc syntax features.

          {3 Sections and Labels}

          We can create sections using {3 Section title} and labels using {3:label_name Section title with label}

          {3 Links and Cross-references}

          External links: {{:https://ocaml.org/} OCaml's official website}

          Cross-references: {!List.length} {{!List.length} Replacement text}

          {3 Inline Formatting}

          {b Bold}, {i Italic}, {e Emphasize}, {^ Superscript}, {_ Subscript}, and [inline code]

          {3 Text Alignment}

          {C Centered text}
          {L Left-aligned text}
          {R Right-aligned text}

          {3 Lists}

          {ol
          {- Ordered list item 1}
          {- Ordered list item 2}
          }

          {ul
          {- Unordered list item 1}
          {- Unordered list item 2}
          }

          - Unordered list item 1
          - Unordered list item 2

          {3 Code Blocks}

          {[
            let square x = x * x
            let result = square 3
          ]}

          {@python[
          def f():
            return 0
          ]}

          {3 Verbatim}

          {v
          This text will be displayed verbatim.
          No formatting will be applied.
          v}

          {3 Module List}

          {!modules: Array List String}

          @param x dividend
          @param divisor

          @return {i quotient}, i.e. result of division
          @raise Division_by_zero raised when divided by zero

          @see <https://en.wikipedia.org/wiki/Arithmetic#Division_(%C3%B7,_or_/)> article
          @see 'arithmetic.ml' for more context

          @since 4.0.0
          @before 4.4.0

          @deprecated use [(/)]

          @version 1.0.0
          @author John Doe *)
      let div x y =
        x / y

      let _ = div 1
    `,
    );

    const items = await querySignatureHelp(Types.Position.create(80, 13));
    expect(items).toMatchInlineSnapshot(`
{
  "activeParameter": 0,
  "activeSignature": 0,
  "signatures": [
    {
      "documentation": {
        "kind": "markdown",
        "value": "This is an example of a docstring that demonstrates various ocamldoc syntax features.

#### Sections and Labels

We can create sections using 

#### Section title

and labels using 

#### Section title with label

#### Links and Cross-references

External links: [OCaml's official website](https://ocaml.org/)

Cross-references: \`List.length\` Replacement text

#### Inline Formatting

**Bold**, *Italic*, *Emphasize*, ^{Superscript}, \\_{Subscript}, and \`inline code\`

#### Text Alignment

Centered text

Left-aligned text

Right-aligned text

#### Lists

1. Ordered list item 1
2. Ordered list item 2

- Unordered list item 1
- Unordered list item 2

- Unordered list item 1
- Unordered list item 2

#### Code Blocks

\`\`\`ocaml
let square x = x * x
let result = square 3
\`\`\`

\`\`\`python
def f():
  return 0
\`\`\`

#### Verbatim

\`\`\`verb
    This text will be displayed verbatim.
    No formatting will be applied.
\`\`\`

#### Module List

* Array
* List
* String

***@param*** \`x\`
dividend

***@param*** divisor

***@return***
*quotient*, i.e. result of division

***@raise*** \`Division_by_zero\`
raised when divided by zero

***@see*** [link](https://en.wikipedia.org/wiki/Arithmetic#Division_\\(%C3%B7,_or_/\\))
article

***@see*** \`arithmetic.ml\`
for more context

***@since*** \`4.0.0\`

***@before*** \`4.4.0\`

***@deprecated***
use \`(/)\`

***@version*** \`1.0.0\`

***@author*** John Doe",
      },
      "label": "div : int -> int -> int",
      "parameters": [
        {
          "label": [
            6,
            9,
          ],
        },
        {
          "label": [
            13,
            16,
          ],
        },
      ],
    },
  ],
}
`);
  });
});
